#include "./ds18b20.h"
#include "../device/gpio.h"
#include "./cmd_uart.h"

namespace driver {

using namespace device;

using DATA = PE<1>;

namespace ec = utils::ec;

Ds18b20::Ds18b20() {

    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN;
    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;

    ConfigOD<DATA>();
    DATA::Set();

    TIM6->CR1 = 0x00;
    TIM6->CR2 = 0x00;
    TIM6->PSC = 84 - 1;
    TIM6->ARR = 0xFFFF;
    TIM6->CR1 = 0x01;
    TIM6->EGR = 0x01;
}

static void delay_us(uint32_t us) {
    TIM6->CNT = 0;
    while (TIM6->CNT < us) {}
}

static bool check_sensor() {
    DATA::Clr();
    delay_us(750);
    DATA::Set();
    delay_us(15);
    uint32_t count = 0;

    do {
        if (!DATA::Read()) {
            break;
        }
        delay_us(1);
        count ++;
    } while (count < 200);

    if (count >= 200) {
        return false;
    }

    count = 0;
    do {
        if (DATA::Read()) {
            break;
        }
        delay_us(1);
        count ++;
    } while (count < 240);

    return count < 240;
}

static bool read_bit() {
    bool val = false;
    DATA::Clr();
    delay_us(2);
    DATA::Set();
    delay_us(12);
    val = DATA::Read();
    delay_us(50);
    return val;
}

static void write_byte(uint8_t value) {
    for (uint8_t i = 0; i < 8; i ++) {
        DATA::Clr();
        if (value & 0x01) {
            delay_us(2);
            DATA::Set();
            delay_us(90);
        } else {
            delay_us(90);
            DATA::Set();
            delay_us(2);
        }
        value >>= 1;
    }
}

static uint8_t read_byte() {
    uint8_t value = 0;
    for (uint8_t i = 0; i < 8; i ++) {
        value >>= 1;
        if (read_bit()) {
            value |= 0x80;
        }
    }
    return value;
}

utils::error_t Ds18b20::read_value(int8_t &value) {
    
    uint8_t tl, th;

    if (! check_sensor() ) {
        return ec::Ds18b20Fail;
    }

    write_byte(0xCC);
    write_byte(0x44);
    if (! check_sensor() ) {
        return ec::Ds18b20Fail;
    }

    write_byte(0xCC);
    write_byte(0xBE);

    tl = read_byte();
    th = read_byte();

    uint16_t val = ((th << 8) + tl) & 0xFFF;
    if (val & 0x800) {
        value = -(0x1000 - val) / 16;
    } else {
        value = val / 16;
    }

    return ec::Ok;
}


}
